﻿using EasyNetQ;
using lib.dmt.project.Models.MQ;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace lib.dmt.teltonika.server.MQ
{
    /// <summary>
    /// 
    /// </summary>
    public class TeltonikaMessagePublisher
    {
        private RabbitMQ.Client.ConnectionFactory? _factory = null;
        private RabbitMQ.Client.IConnection? _connection = null;
        private RabbitMQ.Client.IModel? _channel = null;

        private readonly ILogger? _logger;
        //private readonly IConfiguration? _config;

        private readonly string? rawConnectionString;
        private readonly string? rawConnectionArgument;

        //public TeltonikaMessagePublisher(ILogger? logger, IConfiguration? config)
        //{
        //    this._logger = logger ?? throw new ArgumentNullException(nameof(logger));
        //    this._config = config ?? throw new ArgumentNullException(nameof(config));
        //    InitialRabbitMq();
        //}

        public TeltonikaMessagePublisher(ILogger? logger, string rawConnString, string rawConnArgs)
        {
            if (string.IsNullOrEmpty(rawConnArgs) == true || string.IsNullOrEmpty(rawConnString))
            { 
                throw new ArgumentNullException(nameof(rawConnString));
            }

            this._logger = logger ?? throw new ArgumentNullException(nameof(logger));
            this.rawConnectionString = rawConnString ?? throw new ArgumentNullException(nameof(rawConnString));
            this.rawConnectionArgument = rawConnArgs ?? throw new ArgumentNullException(nameof(rawConnArgs));

            connString = new RabbitMqConnectionString(this.rawConnectionString);
            connArgs = new RabbitMqConnectionArgument(this.rawConnectionArgument);
            InitialRabbitMq();
        }

        private RabbitMqConnectionString? connString = null;
        private RabbitMqConnectionArgument? connArgs = null;

        private void InitialRabbitMq()
        {
            _logger?.LogDebug($"[TeltonikaMessagePublisher]; invoked InitialRabbitMq()");

            if (connString == null)
            {
                throw new Exception($"[TeltonikaMessagePublisher]; failed to create a new RabbitMqConnectionString instance");
            }

            if (connArgs == null)
            {
                throw new Exception($"[TeltonikaMessagePublisher]; failed to create a new RabbitMqConnectionArgument instance");
            }

            _factory = new RabbitMQ.Client.ConnectionFactory
            {
                HostName = connString.GetStringValue("host") ?? "localhost",
                Port = connString.GetIntValue("port") ?? 5672,
                UserName = connString.GetStringValue("username") ?? "guest",
                Password = connString.GetStringValue("password") ?? "guest",
                VirtualHost = connString.GetStringValue("virtualhost") ?? "/"
            };
            if (_factory == null)
            {
                throw new ArgumentNullException(nameof(_factory));
            }
            _logger?.LogTrace($"[TeltonikaMessagePublisher]; RabbitMQ Factory OK; ConnectinoString={rawConnectionString}");

            _connection = _factory.CreateConnection();
            if (_connection == null)
            {
                throw new ArgumentNullException(nameof(_connection));
            }
            _logger?.LogTrace($"[TeltonikaMessagePublisher]; RabbitMQ Connection OK");

            _channel = _connection.CreateModel();
            if (_channel == null)
            {
                throw new ArgumentNullException(nameof(_channel));
            }
            _logger?.LogTrace($"[TeltonikaMessagePublisher]; RabbitMQ Channel OK");


            //declare exchange
            _channel?.ExchangeDeclare(exchange: connString!.GetStringValue("exchange") ?? "__exchange_no_exists",
                type: ExchangeType.Topic,
                durable: true,
                autoDelete: false,
                arguments: connArgs!.Arguments);
            _logger?.LogTrace($"[TeltonikaMessagePublisher]; RabbitMQ Exchange Declare OK; ConnectionArguments={rawConnectionArgument}");
        }

        public Task Publish(string payload)
        {
            if (connString == null || connArgs == null || _factory == null || _connection == null || _channel == null)
            {
                InitialRabbitMq();
            }

            try
            {
                //argument:
                //Dictionary<string, object> qArgs = new Dictionary<string, object>();
                //qArgs.Add("x-message-ttl", 10800000);
                //qArgs.Add("x-dead-letter-exchange", "device.receive.dead.exchange");
                //qArgs.Add("x-dead-letter-routing-key", "device.receive.dead.routing");

                //the queue should be defined FIRST
                //_channel?.QueueDeclare(queue: connString!.GetStringValue("queue") ?? "__queue_no_exists", 
                //    durable: true, 
                //    arguments: connArgs!.Arguments ?? null);

                var body = Encoding.UTF8.GetBytes(payload);

                // Enable publisher confirms for reliability
                _channel?.ConfirmSelect();

                // Publish message with mandatory flag set to false
                _channel?.BasicPublish(exchange: connString!.GetStringValue("exchange") ?? "__exchange_no_exists", 
                    routingKey: connString!.GetStringValue("routingkey") ?? "__exchange_no_exists" , 
                    mandatory: false, 
                    basicProperties: null, body: body);

                // Wait for confirmation from broker
                _channel?.WaitForConfirmsOrDie(timeout: TimeSpan.FromSeconds(5));

                return Task.CompletedTask;
            }
            catch (Exception ex)
            {
                _logger?.LogCritical(ex, $"[TeltonikaMessagePublisher]; message publication failed: {ex.Message}");
                //throw; // Re-throw to let caller handle the error
                return Task.CompletedTask;
            }
        }
    }
}
